package com.coolingme.springframework.webmvc.servlet;

import com.coolingme.springframework.context.MeApplicationContext;
import com.coolingme.springframework.annotation.MeController;
import com.coolingme.springframework.annotation.MeRequestMapping;
import com.coolingme.springframework.annotation.MeRequestParam;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 负责任务调度，请求分发
 *
 * @author wangyue
 * @date 2020/11/10 15:40
 */
public class MeDispatcherServlet extends HttpServlet {

    /**
     * Spring核心IoC容器
     */
    private MeApplicationContext applicationContext;

    /**
     * 保存 url 和 方法对象Method的映射关系，暂不使用
     */
    private Map<String, Method> handlerMappingMap = new HashMap<>();

    /**
     * 保存 url 和 方法对象Method的映射关系，封装为对象
     */
    private List<MeHandlerMapping> handlerMappingList = new ArrayList<>();


    /**
     * MeHandlerMapping -> MeHandlerAdapter
     */
    private Map<MeHandlerMapping, MeHandlerAdapter> handlerAdapters = new HashMap<>();


    /**
     * 视图解析器列表
     * <p>
     * templateRootDir = D:\workspace\XXXX\spring-2.0-ioc\target\classes\layouts
     */
    private List<MeViewResolver> viewResolvers = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            // 根据URL找到对应方法并执行，委派模式
//            doDispatch(req, resp);
//            doDispatchNew(req, resp);

            doDispatchMVC(req, resp);

        } catch (Exception e) {
            e.printStackTrace();
            // 响应500
//            resp.getWriter().write("500 Exception,Detail:" + Collections.singletonList(e.getMessage()));

            Map<String, Object> model = new HashMap<String, Object>();
            model.put("detail", "500 Exception,Detail: ");
            model.put("stackTrace", Arrays.toString(e.getStackTrace()));
            try {
                // 根据视图去调度
                processDispatchResult(req, resp, new MeModelAndView("500", model));
            } catch (Exception exception) {
                exception.printStackTrace();
            }
        }
    }

    private void doDispatchMVC(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        //1、根据URL 拿到对应的Handler
        MeHandlerMapping handler = getHandler(req);

        if (null == handler) {
            // 没有匹配到，就跳转404页面
            processDispatchResult(req, resp, new MeModelAndView("404"));
            return;
        }

        //2、根据HandlerMapping拿到HandlerAdapter
        MeHandlerAdapter ha = getHandlerAdapter(handler);

        //3、根据HandlerAdapter拿到对应的ModelAndView
        MeModelAndView mv = ha.handle(req, resp, handler);

        //4、根据ViewResolver找到对应View对象
        // 通过View对象渲染页面，并返回
        processDispatchResult(req, resp, mv);

    }

    private MeHandlerMapping getHandler(HttpServletRequest req) {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        // 请求的URL
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

        for (MeHandlerMapping handlerMapping : this.handlerMappingList) {
            // 匹配URL
            Matcher matcher = handlerMapping.getPattern().matcher(url);
            if (!matcher.matches()) {
                continue;
            }
            // 返回匹配到的HandlerMapping
            return handlerMapping;
        }
        return null;
    }


    private MeHandlerAdapter getHandlerAdapter(MeHandlerMapping handler) {
        if (this.handlerAdapters.isEmpty()) {
            return null;
        }
        MeHandlerAdapter ha = this.handlerAdapters.get(handler);
        return ha;
    }

    private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, MeModelAndView mv) throws Exception {
        // 没有视图，直接返回结果
        if (null == mv) {
            return;
        }
        if (this.viewResolvers.isEmpty()) {
            return;
        }

        // 遍历视图解析器
        for (MeViewResolver viewResolver : this.viewResolvers) {
            // 解析视图名称（index.html），获取视图对象
            MeView view = viewResolver.resolveViewName(mv.getViewName());
            if (null == view) {
                continue;
            }

            // 渲染视图
            view.render(mv.getModel(), req, resp);
            return;
        }
    }


    private void doDispatchNew(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

        if (!this.handlerMappingMap.containsKey(url)) {
            resp.getWriter().write("404 Not Found!!!");
            return;
        }

        Method method = this.handlerMappingMap.get(url);

        //1、先把形参的位置和参数名字建立映射关系，并且缓存下来
        Map<String, Integer> paramIndexMapping = new HashMap<String, Integer>();

        Annotation[][] pa = method.getParameterAnnotations();
        for (int i = 0; i < pa.length; i++) {
            for (Annotation a : pa[i]) {
                if (a instanceof MeRequestParam) {
                    String paramName = ((MeRequestParam) a).value();
                    if (!"".equals(paramName.trim())) {
                        paramIndexMapping.put(paramName, i);
                    }
                }
            }
        }

        Class<?>[] paramTypes = method.getParameterTypes();
        for (int i = 0; i < paramTypes.length; i++) {
            Class<?> type = paramTypes[i];
            if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
                paramIndexMapping.put(type.getName(), i);
            }
        }

        //2、根据参数位置匹配参数名字，从url中取到参数名字对应的值
        Object[] paramValues = new Object[paramTypes.length];

        //http://localhost/demo/query?name=Tom&name=Tomcat&name=Mic
        Map<String, String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> param : params.entrySet()) {
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", "");

            if (!paramIndexMapping.containsKey(param.getKey())) {
                continue;
            }

            int index = paramIndexMapping.get(param.getKey());

            //涉及到类型强制转换
            paramValues[index] = value;
        }

        if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            int index = paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = req;
        }

        if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            int index = paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }

        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        //3、组成动态实际参数列表，传给反射调用
        method.invoke(applicationContext.getBean(beanName), paramValues);
    }

    /**
     * 请求路径调度
     *
     * @param req  HttpServletRequest
     * @param resp HttpServletResponse
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        // uri
        String uri = req.getRequestURI();
        // 上下文环境（项目名）
        String contextPath = req.getContextPath();
        // 请求路径,去掉项目名和重复的 /
        String url = uri.replace(contextPath, "").replaceAll("/+", "/");

        // 网站图标，直接返回
        String favicon = "/favicon.ico";
        if (favicon.equals(url)) {
            return;
        }
        // 未找到对应的请求路径
        if (!handlerMappingMap.containsKey(url)) {
            resp.getWriter().write("404 not find!");
            return;
        }

        // 获取方法对象
        Method method = handlerMappingMap.get(url);

        // 1.参数动态获取
        // url参数Map
        Map<String, String[]> parameterMap = req.getParameterMap();

        // 硬编码获取方法参数
//        Object[] params = new Object[]{
//                req, resp, parameterMap.get("name")[0]
//        };

        // 实参列表要根据形参才能决定，首先拿到形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();

        // 定义实参列表
        Object[] params = new Object[parameterTypes.length];
        // 根据每个形参，获取实参
        for (int i = 0; i < params.length; i++) {
            // 形参类型
            Class<?> parameterType = parameterTypes[i];

            // 类型等于request
            if (parameterType == HttpServletRequest.class) {
                params[i] = req;
                continue;
            } else if (parameterType == HttpServletResponse.class) {
                // 类型等于response
                params[i] = resp;
                continue;
            } else if (parameterType == String.class) {
                // 获取方法的参数注解，第一个纬度为索引，第二个维度为注解，二维数组的长度等于参数的长度
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();

                // 拿到第i个形参对应的注解
                for (Annotation annotation : parameterAnnotations[i]) {
                    if (annotation instanceof MeRequestParam) {
                        // 获取MeRequestParam的value，即自定义的实参name
                        String paramName = ((MeRequestParam) annotation).value();
                        // 根据注解的name值，从url参数中获取实参值
                        if (!"".equals(paramName.trim())) {
                            String value = Arrays.toString(parameterMap.get(paramName))
                                    // 去除 []
                                    .replaceAll("\\[|\\]", "")
                                    // 去除空白
                                    .replaceAll("\\s", "");
                            params[i] = value;
                        }
                    }
                }
            }
        }

        // 2.通过方法获取所在类实例
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        Object instance = this.applicationContext.getBean(beanName);

        // 3.通过反射调用方法
        method.invoke(instance, params);
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1.初始化Spring核心IoC容器
        applicationContext = new MeApplicationContext(config.getInitParameter("contextConfigLocation"));

        //==============MVC部分==============
        // 2. 初始化路径方法映射HandlerMapping [MVC]
//        doInitHandlerMapping();

        // MVC初始化策略
        initStrategies(applicationContext);


        System.out.println("Spring framework is init.");
    }

    private void initStrategies(MeApplicationContext applicationContext) {
        // 1. 初始化handlerMapping
        initHandlerMappings(applicationContext);

        // 2. 初始化参数适配器
        initHandlerAdapters(applicationContext);

        // 3. 初始化视图转换器
        initViewResolvers(applicationContext);

        System.out.println("MVC initStrategies done.");
    }

    private void initHandlerMappings(MeApplicationContext applicationContext) {
        // 初始化路径方法映射HandlerMapping
        doInitHandlerMapping();
    }

    /**
     * 5、初始化HandlerMapping
     * 请求路径URL 和方法Method 一对一对应
     */
    private void doInitHandlerMapping() {
        // 容器为空，不需要映射
        if (this.applicationContext.getBeanDefinitionCount() == 0) {
            return;
        }

        // 循环IOC容器
        for (String className : this.applicationContext.getBeanDefinitionNames()) {
            // 实例的类对象
            Object instance = this.applicationContext.getBean(className);
            Class<?> clazz = instance.getClass();

            // 只获取Controller
            if (!clazz.isAnnotationPresent(MeController.class)) {
                continue;
            }

            // 获取类上的路径
            String classUrl = "";
            if (clazz.isAnnotationPresent(MeRequestMapping.class)) {
                classUrl = clazz.getAnnotation(MeRequestMapping.class).value();
            }

            // 默认获取public的方法
            for (Method method : clazz.getMethods()) {
                // 只获取有MeRequestMapping注解的方法
                if (!method.isAnnotationPresent(MeRequestMapping.class)) {
                    continue;
                }
                // 获取方法上的路径
                String methodUrl = method.getAnnotation(MeRequestMapping.class).value();

                /*
                // 拼接URL，去除重复的 / ,如 //user//get
                String url = ("/" + classUrl + "/" + methodUrl).replaceAll("/+", "/");
                // 存入映射关系容器中
                handlerMappingMap.put(url, method);
                System.out.println("Mapped " + className + " => " + url + " : " + method.getName());
                */

                //  //demo//query
                String regex = ("/" + classUrl + "/" + methodUrl)
                        // 将 * 替换为 .*，匹配通配符
                        .replaceAll("\\*", ".*")
                        // 去除重复的 /
                        .replaceAll("/+", "/");
                // 将URL转为正则
                Pattern pattern = Pattern.compile(regex);
                // 存入映射关系容器中
                this.handlerMappingList.add(new MeHandlerMapping(pattern, instance, method));

                System.out.println("Mapped " + className + " => " + regex + " : " + method.getName());

            }
        }
    }

    private void initHandlerAdapters(MeApplicationContext applicationContext) {
        for (MeHandlerMapping handlerMapping : handlerMappingList) {
            this.handlerAdapters.put(handlerMapping, new MeHandlerAdapter());
        }
    }

    private void initViewResolvers(MeApplicationContext applicationContext) {
        // MVC模板根目录
        String templateRoot = applicationContext.getConfig().getProperty("templateRoot");

        // 模板根目录的绝对路径
//        String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
//
//        File templateRootDir = new File(templateRootPath);
//        for (File file : templateRootDir.listFiles()) {
//        }

        // 视图解析器，这里就一个目录，所以就直接添加了
        this.viewResolvers.add(new MeViewResolver(templateRoot));
    }


    /**
     * 将类名首字母小写
     *
     * @param simpleName 类名
     * @return 小写类名
     */
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        // 且大写字母的ASCII码要小于小写字母的ASCII码32
        chars[0] += 32;
        return String.valueOf(chars);
    }

}
